/**    
 * Copyright (C) 2006, Laboratorio di Valutazione delle Prestazioni - Politecnico di Milano

 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

package jmt.gui.jaba;

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.io.IOException;
import java.util.ArrayList;

import javax.swing.AbstractButton;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.KeyStroke;

import jmt.common.exception.InputDataException;
import jmt.common.exception.SolverException;
import jmt.framework.gui.components.JMTMenuBar;
import jmt.framework.gui.components.JMTToolBar;
import jmt.framework.gui.help.HoverHelp;
import jmt.framework.gui.listeners.AbstractJMTAction;
import jmt.framework.gui.listeners.MenuAction;
import jmt.framework.gui.wizard.Wizard;
import jmt.framework.gui.wizard.WizardPanel;
import jmt.gui.common.CommonConstants;
import jmt.gui.common.panels.AboutDialogFactory;
import jmt.gui.common.panels.WarningWindow;
import jmt.gui.common.resources.JMTImageLoader;
import jmt.gui.common.xml.ModelLoader;
import jmt.gui.exact.panels.ForceUpdatablePanel;
import jmt.gui.jaba.link.SolverDispatcher;
import jmt.gui.jaba.panels.AllInOnePanel;
import jmt.gui.jaba.panels.ClassesPanel;
import jmt.gui.jaba.panels.ConvexHullPanel;
import jmt.gui.jaba.panels.DescriptionPanel;
import jmt.gui.jaba.panels.PerformanceIndicesPanel;
import jmt.gui.jaba.panels.SectorsGraphicPanel;
import jmt.gui.jaba.panels.SectorsTextualPanel;
import jmt.gui.jaba.panels.ServiceDemandsPanel;
import jmt.gui.jaba.panels.ServiceTimesPanel;
import jmt.gui.jaba.panels.StationsPanel;
import jmt.gui.jaba.panels.VisitsPanel;
import jmt.manual.ManualBookmarkers;
import jmt.manual.PDFViewerBuffer;

/**
 * This is the object you use to define your system structure and parameters
 * 
 * @author alyf (Andrea Conti)
 * @version Date: 11-set-2003 Time: 14.47.11
 * 
 *          Adapted by Andrea Zanzottera Heavily bugfixed by Bertoli Marco
 */
public class JabaWizard extends Wizard {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	private final static boolean DEBUG = false;

	private JabaModel data;
	private JLabel helpLabel;
	private HoverHelp help;

	private ModelLoader modelLoader = new ModelLoader(ModelLoader.JABA);

	private SolverDispatcher jsolver;

	// keep a reference to these three components to enable switching
	private WizardPanel serviceTimesPanel, serviceDemandsPanel, visitsPanel,
			performancePanel;
	private ConvexHullPanel hullPanel;

	private AllInOnePanel allInOnePanel;

	public JabaWizard() {
		this(new JabaModel());
	}

	public JabaWizard(JabaModel data) {
		super("JABA");

		System.out.flush();
		setSize(800, 600);
		this.centerWindow();
		setIconImage(JMTImageLoader.loadImageAwt("JABAIcon"));
		this.data = data;
		data.resetChanged();
		this.setJMenuBar(makeMenubar());
		getContentPane().add(makeToolbar(), BorderLayout.NORTH);
		addPanel(new ClassesPanel(this));
		addPanel(new StationsPanel(this));
		serviceTimesPanel = new ServiceTimesPanel(this);
		visitsPanel = new VisitsPanel(this);
		serviceDemandsPanel = new ServiceDemandsPanel(this);
		addPanel(serviceDemandsPanel);
		addPanel(new DescriptionPanel(this));

		// NEW Andrea Zanzottera 25/11/2005
		// Pannello con i risultati grafici
		SectorsGraphicPanel drawPanel = new SectorsGraphicPanel();
		drawPanel.setData(this.data);
		addPanel(drawPanel);
		// Pannello con i risultati in numeri

		// END Andrea Zanzottera 25/11/2005

		// New Carlo Gimondi
		hullPanel = new ConvexHullPanel(this);
		hullPanel.setData(this.data);
		addPanel(hullPanel);
		// End New Carlo Gimondi

		// NEW Sebastiano Spicuglia 2010/12/04
		performancePanel = new PerformanceIndicesPanel(this);
		performancePanel.repaint();
		addPanel(performancePanel);
		// END Sebastiano Spicuglia 2010/12/04

		// NEW Sebastiano Spicuglia 2011/18/05
		allInOnePanel = new AllInOnePanel(this);
		allInOnePanel.repaint();
		addPanel(allInOnePanel);
		// END Sebastiano Spicuglia 2011/18/05

		addPanel(new SectorsTextualPanel(this));
		this.setVisible(true);
	}

	private AbstractJMTAction FILE_SAVE = new AbstractJMTAction("Save...") {
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;

		{
			putValue(Action.SHORT_DESCRIPTION, "Save Model");
			setIcon("Save", JMTImageLoader.getImageLoader());
			putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(
					KeyEvent.VK_S, ActionEvent.CTRL_MASK));
			putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_S));
		}

		public void actionPerformed(ActionEvent e) {
			save();
		}
	};

	private AbstractJMTAction FILE_OPEN = new AbstractJMTAction("Open...") {
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;

		{
			putValue(Action.SHORT_DESCRIPTION, "Open Saved Model");
			setIcon("Open", JMTImageLoader.getImageLoader());
			putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(
					KeyEvent.VK_O, ActionEvent.CTRL_MASK));
			putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_O));
		}

		public void actionPerformed(ActionEvent e) {
			open();
		}
	};

	private AbstractJMTAction FILE_NEW = new AbstractJMTAction("New...") {
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;

		{
			putValue(Action.SHORT_DESCRIPTION, "Create New Model");
			setIcon("New", JMTImageLoader.getImageLoader());
			putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(
					KeyEvent.VK_N, ActionEvent.CTRL_MASK));
			putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_N));
		}

		public void actionPerformed(ActionEvent e) {
			newModel();
		}
	};

	private AbstractJMTAction FILE_EXIT = new AbstractJMTAction("Exit") {
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;

		{
			putValue(Action.SHORT_DESCRIPTION, "Exits Application");
			putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(
					KeyEvent.VK_Q, ActionEvent.CTRL_MASK));
			putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_Q));
		}

		public void actionPerformed(ActionEvent e) {
			close();
		}
	};

	private AbstractJMTAction SWITCH_TO_SIMULATOR = new AbstractJMTAction(
			"Import in JSIM...") {
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;

		{
			putValue(Action.SHORT_DESCRIPTION,
					"Import current model into JSIMwiz...");
			setIcon("toJSIM", JMTImageLoader.getImageLoader());
			putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(
					KeyEvent.VK_G, ActionEvent.CTRL_MASK));
			putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_G));
		}

		public void actionPerformed(ActionEvent e) {
			switchToSimulator();
		}
	};

	private AbstractJMTAction ACTION_RANDOMIZE_MODEL = new AbstractJMTAction(
			"Randomize") {
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;

		{
			putValue(Action.SHORT_DESCRIPTION,
					"Random generation of service demands in the interval [0, 100]");
			setIcon("dice", JMTImageLoader.getImageLoader());
			putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(
					KeyEvent.VK_R, ActionEvent.CTRL_MASK));
			putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_R));
		}

		public void actionPerformed(ActionEvent e) {
			randomizeModel();
		}
	};


	private AbstractJMTAction HELP = new AbstractJMTAction("JABA help") {
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;

		{
			putValue(Action.SHORT_DESCRIPTION, "Show JABA help");
			setIcon("Help", JMTImageLoader.getImageLoader());
			putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(
					KeyEvent.VK_H, ActionEvent.CTRL_MASK));
			putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_H));
		}

		public void actionPerformed(ActionEvent e) {
			Runnable r = new Runnable() {
				public void run() {
					try {
						new PDFViewerBuffer("JABA manual", ManualBookmarkers.JABA);
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			};
			EventQueue.invokeLater(r);

		}
	};

	private AbstractJMTAction ABOUT = new AbstractJMTAction("About JABA") {
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;

		{
			putValue(Action.SHORT_DESCRIPTION, "About JABA");
			/*
			 * putValue(Action.SMALL_ICON, ImageLoader.loadImage("helpIcon"));
			 * putValue(Action.ACCELERATOR_KEY,
			 * KeyStroke.getKeyStroke(KeyEvent.VK_H, ActionEvent.ALT_MASK));
			 * putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_H));
			 */
		}

		public void actionPerformed(ActionEvent e) {
			showAbout();
		}
	};

	private AbstractJMTAction ACTION_SOLVE = new AbstractJMTAction("Solve") {
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;

		{
			putValue(Action.SHORT_DESCRIPTION, "Solve model");
			setIcon("Sim", JMTImageLoader.getImageLoader());

			putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(
					KeyEvent.VK_L, ActionEvent.CTRL_MASK));
			putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_L));

		}

		public void actionPerformed(ActionEvent e) {

			if (checkFinish()) {
				finish();
			}

		}
	};

	/**
	 * @return the toolbar for the jaba wizard. Shamelessly uses icon from the
	 *         main jmt frame
	 */
	protected JMTToolBar makeToolbar() {

		JMTToolBar tb = new JMTToolBar(JMTImageLoader.getImageLoader());
		tb.setFloatable(false);

		// null values add a gap between toolbar icons
		AbstractJMTAction[] actions = { FILE_NEW, FILE_OPEN, FILE_SAVE, null,
				ACTION_SOLVE, SWITCH_TO_SIMULATOR, ACTION_RANDOMIZE_MODEL
				, null, HELP };
		String[] htext = {
				"Creates a new model",
				"Opens a saved model",
				"Saves the current model",
				"Solves the current model",
				"Import current model to JSIMwiz to solve it with the simulator",
				"Randomize model data", "Show help" };
		ArrayList<AbstractButton> buttons = tb.populateToolbar(actions);
		// Adds help
		for (int i = 0; i < buttons.size(); i++) {
			AbstractButton button = (AbstractButton) buttons.get(i);
			help.addHelp(button, htext[i]);
		}
		return tb;
	}

	private JMTMenuBar makeMenubar() {
		JMTMenuBar jmb = new JMTMenuBar(JMTImageLoader.getImageLoader());
		AbstractJMTAction[] menuItems = new AbstractJMTAction[] {
				new MenuAction("File", new AbstractJMTAction[] { FILE_NEW,
						FILE_OPEN, FILE_SAVE, null, FILE_EXIT }),
				new MenuAction("Action", new AbstractJMTAction[] {
						ACTION_SOLVE, ACTION_RANDOMIZE_MODEL, null,
						SWITCH_TO_SIMULATOR, null, ACTION_NEXT, ACTION_PREV }),
				new MenuAction("Help", new AbstractJMTAction[] { HELP, null,
						ABOUT }), };

		jmb.populateMenu(menuItems);
		return jmb;
	}

	/**
	 * @return the button panel
	 */
	@Override
	protected JComponent makeButtons() {
		help = new HoverHelp();
		helpLabel = help.getHelpLabel();

		helpLabel.setBorder(BorderFactory.createEtchedBorder());
		// helpLabel.setHorizontalAlignment(SwingConstants.CENTER);

		ACTION_FINISH.putValue(Action.NAME, "Solve");
		ACTION_CANCEL.putValue(Action.NAME, "Exit");

		JPanel buttons = new JPanel();

		JButton button_finish = new JButton(ACTION_FINISH);
		help.addHelp(button_finish,
				"Validates the system and starts the solver");
		JButton button_cancel = new JButton(ACTION_CANCEL);
		help.addHelp(button_cancel, "Exits the wizard discarding all changes");
		JButton button_next = new JButton(ACTION_NEXT);
		help.addHelp(button_next, "Moves on to the next step");
		JButton button_previous = new JButton(ACTION_PREV);
		help.addHelp(button_previous, "Goes back to the previous step");
		buttons.add(button_previous);
		buttons.add(button_next);
		buttons.add(button_finish);
		buttons.add(button_cancel);
	

		JPanel labelbox = new JPanel();
		labelbox.setLayout(new BorderLayout());
		labelbox.add(Box.createVerticalStrut(20), BorderLayout.WEST);
		labelbox.add(helpLabel, BorderLayout.CENTER);

		Box buttonBox = Box.createVerticalBox();
		buttonBox.add(buttons);
		buttonBox.add(labelbox);
		return buttonBox;
	}

	// BEGIN Federico Dall'Orso 8/3/2005
	// NEW
	private void newModel() {
		currentPanel.lostFocus();
		if (checkForSave("<html>Save changes before creating a new model?</html>")) {
			return;
		}
		Rectangle bounds = this.getBounds();
		JabaWizard ew = new JabaWizard();
		ew.setBounds(bounds);
		ew.setVisible(true);
		this.setVisible(false);
		this.dispose();
	}

	// END Federico Dall'Orso 8/3/2005
	/**
	 * Shows a confirmation dialog to save before new model or exit operations
	 * 
	 * @param msg
	 *            The message to display.
	 * @return <code>true</code> - if the user select cancel button.
	 */
	public boolean checkForSave(String msg) {
		// Checks if there's an old graph to save
		if (data != null && data.isChanged()) {
			int resultValue = JOptionPane.showConfirmDialog(this, msg,
					"JABA - Warning", JOptionPane.YES_NO_CANCEL_OPTION,
					JOptionPane.WARNING_MESSAGE);
			if (resultValue == JOptionPane.YES_OPTION) {
				save();
				return true;
			}
			if (resultValue == JOptionPane.CANCEL_OPTION) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Saves current model <br>
	 * Author: Bertoli Marco
	 */
	private void save() {
		currentPanel.lostFocus();
		if (!checkFinish()) {
			return; // panels with problems are expected to notify the user by
					// themselves
		}
		int retval = modelLoader.saveModel(data, this, null);
		switch (retval) {
		case ModelLoader.SUCCESS:
			data.resetChanged();
			this.setTitle("JABA - "
					+ modelLoader.getSelectedFile().getAbsolutePath());
			break;
		case ModelLoader.FAILURE:
			JOptionPane.showMessageDialog(this,
					modelLoader.getFailureMotivation(), "Error",
					JOptionPane.ERROR_MESSAGE);
			break;
		}
	}

	/**
	 * Opens a new model <br>
	 * Author: Bertoli Marco
	 */
	@SuppressWarnings("unchecked")
	private void open() {
		currentPanel.lostFocus();
		if (checkForSave("<html>Save changes before opening a saved model?</html>")) {
			return;
		}
		JabaModel newdata = new JabaModel();
		int retval = modelLoader.loadModel(newdata, this);
		switch (retval) {
		case ModelLoader.SUCCESS:
		case ModelLoader.WARNING:
			data = newdata;
			currentPanel.gotFocus();
			// Shows right panels
			if (data.areVisitsSet()) {
				removePanel(serviceDemandsPanel);
				((ForceUpdatablePanel) serviceTimesPanel).retrieveData();
				((ForceUpdatablePanel) visitsPanel).retrieveData();
				addPanel(visitsPanel, 2);
				addPanel(serviceTimesPanel, 2);
			} else {
				removePanel(visitsPanel);
				removePanel(serviceTimesPanel);
				((ForceUpdatablePanel) serviceDemandsPanel).retrieveData();
				addPanel(serviceDemandsPanel, 2);
			}
			tabbedPane.setSelectedIndex(0);
			this.setTitle("JABA - " + modelLoader.getSelectedFile().getName());
			break;
		case ModelLoader.FAILURE:
			JOptionPane.showMessageDialog(this,
					modelLoader.getFailureMotivation(), "Error",
					JOptionPane.ERROR_MESSAGE);
			break;

		}
		updatePanels();

		// Shows warnings if any
		if (retval == ModelLoader.WARNING) {
			new WarningWindow(modelLoader.getLastWarnings(), this,
					modelLoader.getInputFileFormat(), CommonConstants.JABA)
					.show();
		}
		updatePanels();
	}

	public JabaModel getData() {
		return data;
	}

	@Override
	protected void finish() {
		// OLD
		// do not call this method!!! It's already called inside checkFinish()
		// method.
		// currentPanel.lostFocus();
		solve();
	}

	@Override
	protected boolean cancel() {
		if (currentPanel != null) {
			currentPanel.lostFocus();
		}
		return !checkForSave("<html>Save changes before closing?</html>");
	}

	protected void switchToSimulator() {
		cancel();
		new jmt.gui.jmodel.mainGui.MainWindow();
		dispose();
	}

	public HoverHelp getHelp() {
		return help;
	}

	/**
	 * switches service times and visits panels to service demands panel in
	 * order to change data representation.
	 */
	public void switchFromSTVtoSD() {
		((ForceUpdatablePanel) serviceTimesPanel).commitData();
		((ForceUpdatablePanel) visitsPanel).retrieveData();
		((ForceUpdatablePanel) visitsPanel).commitData();
		removePanel(serviceTimesPanel);
		removePanel(visitsPanel);
		((ForceUpdatablePanel) serviceDemandsPanel).retrieveData();
		addPanel(serviceDemandsPanel, 2);
	}

	/**
	 * switches service times and visits panels to service demands panel in
	 * order to change data representation.
	 */
	public void switchFromSDtoSTV() {
		((ForceUpdatablePanel) serviceDemandsPanel).commitData();
		removePanel(serviceDemandsPanel);
		((ForceUpdatablePanel) serviceTimesPanel).retrieveData();
		((ForceUpdatablePanel) visitsPanel).retrieveData();
		addPanel(visitsPanel, 2);
		addPanel(serviceTimesPanel, 2);
	}

	public void solve() {

		// New Carlo Gimondi
		int selectTab = 4;
		if (tabbedPane.getSelectedIndex() > 4) {
			selectTab = tabbedPane.getSelectedIndex();
		}
		// End New Carlo Gimondi

		// NEW Zanzottera
		if (jsolver == null) {
			jsolver = new SolverDispatcher();
		}
		JabaModel newdata = new JabaModel(data); // Yields the mean performance
													// indices

		try {
			// controllo che ci siano 2 o 3 classi
			// NEW Zanzottera
			newdata.setResults(jsolver.solve(newdata));
			// end NEW

			// @author Stefano Omini
		} catch (InputDataException rse) {
			JOptionPane.showMessageDialog(this, rse.getMessage(),
					"Input data error", JOptionPane.ERROR_MESSAGE);
			return;
			// end NEW
		} catch (SolverException e) {
			JOptionPane.showMessageDialog(this, e.getMessage(), "Solver error",
					JOptionPane.ERROR_MESSAGE);
			return;
		}
		this.data = newdata;

		// Zanzottera: i risultati vengono visualizzati all'interno della
		// schermata principale
		/*
		 * //Dall'Orso createSolutionWindow(newdata.getResults()); //END
		 */

		updatePanels();
		currentPanel.gotFocus();
		// Select graphical results panel (before select textual one to avoid a
		// graphical glitch)

		// Modify Carlo Gimondi

		// Remove when 3 class Utilization Panel will create
		if (data.getClasses() == 3) {
			tabbedPane.setEnabledAt(tabbedPane.getComponentCount() - 2, false);
			tabbedPane.setEnabledAt(tabbedPane.getComponentCount() - 3, false);
		} else {
			tabbedPane.setEnabledAt(tabbedPane.getComponentCount() - 2, true);
			tabbedPane.setEnabledAt(tabbedPane.getComponentCount() - 3, true);
		}

		tabbedPane.setSelectedIndex(selectTab);
		repaint();
		// End Modify Carlo Gimondi
	}

	// NEW
	// @author Bertoli Marco
	private void showAbout() {
		AboutDialogFactory.showJABA(this);
	}

	// end NEW

	// randomizes model data
	private void randomizeModel() {
		if (DEBUG) {
			System.out.println("Classes: " + data.getClasses() + "; Stations: "
					+ data.getStations());
		}
		// first get infos about classes and station
		for (int i = 0; i < panels.size() && i < 2; i++) {
			Object o = panels.get(i);
			if (o instanceof ForceUpdatablePanel) {
				((ForceUpdatablePanel) o).commitData();
			}
		}
		// then randomize data
		data.randomizeModelData();
		// and then update all those data into panels
		ForceUpdatablePanel[] fuPanes = {
				(ForceUpdatablePanel) serviceDemandsPanel,
				(ForceUpdatablePanel) serviceTimesPanel,
				(ForceUpdatablePanel) serviceDemandsPanel };
		for (ForceUpdatablePanel fuPane : fuPanes) {
			fuPane.retrieveData();
		}
		repaint();
	}

	// END

	private void updatePanels() {
		if (data == null) {
			return;
		}

		for (int i = 0; i < panelCount; i++) {
			panels.get(i).setData(data);
			panels.get(i).redraw();
		}
	}

	@Override
	protected void updateActions() {
		super.updateActions();
		if (currentIndex < (panelCount - 1)) {
			if (!tabbedPane.isEnabledAt(currentIndex + 1)) {
				ACTION_NEXT.setEnabled(false);
			}
		}
		if (currentIndex > 0) {
			if (!tabbedPane.isEnabledAt(currentIndex - 1)) {
				ACTION_PREV.setEnabled(false);
			}
		}
		updatePanels();
	}

	// JABA MAIN
	public static void main(String[] args) {
		new JabaWizard(new JabaModel());
	}

}
